﻿namespace SenenFernandez.SmartExtensionMethods
{
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.Linq;
    using System.Runtime.InteropServices;
    using System.Runtime.Versioning;
    using System.Text.RegularExpressions;
    using System.Windows;

    /// <summary>
    /// Screen Manager for Windows Presentation Foundation.
    /// </summary>
    public static class SmartScreenManager
    {
        private static readonly HandleRef NullHandleReference = new HandleRef(null, IntPtr.Zero);

        [DllImport(User32.Path, ExactSpelling = true)]
        [ResourceExposure(ResourceScope.None)]
        private static extern bool EnumDisplayMonitors(HandleRef handle, IntPtr clip, DisplayEnumProc lpfnEnum, IntPtr data);

        private delegate bool DisplayEnumProc(IntPtr display, IntPtr handle, IntPtr parentDisplay, IntPtr parameters);

        /// <summary>
        /// Enumerates display monitors that intersect a region formed by the intersection of a specified clipping rectangle and the visible region of a device context.
        /// </summary>
        public static IEnumerable<Display> Monitors
        {
            get
            {
                var closure = new DisplayCallback();
                var proc = new DisplayEnumProc(closure.Callback);
                EnumDisplayMonitors(NullHandleReference, IntPtr.Zero, proc, IntPtr.Zero);
                return closure.Monitors;
            }
        }

        /// <summary>
        /// Gets the default display monitor.
        /// </summary>
        public static Display Primary
        {
            get { return Monitors.Where(source => source.IsPrimary).FirstOrDefault(); }
        }

        /// <summary>
        /// Gets display monitor by name.
        /// </summary>
        public static Display GetMonitor(this string displayName)
        {
            return Monitors.FirstOrDefault(source => source.Name.ToUpperOrEmpty().Contains(displayName.ToUpperOrEmpty()));
        }

        /// <summary>
        /// Gets display monitor.
        /// </summary>
        public static Display GetMonitorOrDefault(this string displayName)
        {
            return GetMonitor(displayName) ?? Primary;
        }

        /// <summary>
        /// Gets display monitor by WindowId.
        /// </summary>
        public static Display GetMonitorFromWindow(this IntPtr windowId)
        {
            var monitorIdFromWindow = windowId.MonitorFromWindow();
            return Monitors.FirstOrDefault(source => source.Id == monitorIdFromWindow);
        }
    }

    public class Display
    {
        private const int DisplayinfofPrimary = 0x00000001;

        /// <summary>
        /// Display Id.
        /// </summary>
        public IntPtr Id { get; private set; }

        /// <summary>
        /// Screen Area.
        /// </summary>
        public Rect Bounds { get; private set; }

        /// <summary>
        /// Working Area.
        /// </summary>
        public Rect WorkingArea { get; private set; }

        /// <summary>
        /// Display Name.
        /// </summary>
        public string Name { get; private set; }

        /// <summary>
        /// IsPrimary Display.
        /// </summary>
        public bool IsPrimary { get; private set; }

        [DllImport(User32.Path, CharSet = CharSet.Auto)]
        [ResourceExposure(ResourceScope.None)]
        private static extern bool GetMonitorInfo(HandleRef handle, [In, Out] DisplayInfoEx info);

        public Display(IntPtr display, IntPtr handle)
        {
            var info = new DisplayInfoEx();
            GetMonitorInfo(new HandleRef(null, display), info);
            Bounds = new Rect(
                info.Display.Left, info.Display.Top,
                info.Display.Width - info.Display.Left,
                info.Display.Height - info.Display.Top);
            WorkingArea = new Rect(
                info.Work.Left, info.Work.Top,
                info.Work.Width - info.Work.Left,
                info.Work.Height - info.Work.Top);
            Id = display;
            IsPrimary = ((info.Flags & DisplayinfofPrimary) != 0);
            Name = Regex.Replace(info.Name, "[^A-Za-z0-9]", "");
        }

        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto, Pack = 4)]
        private class DisplayInfoEx
        {
            internal int Size = Marshal.SizeOf(typeof(DisplayInfoEx));
            internal Rectangle Display;
            internal Rectangle Work;
            internal int Flags;
            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
            internal readonly string Name = string.Empty;
        }
    }
    
    internal class DisplayCallback
    {
        private List<Display> monitors;

        public IEnumerable<Display> Monitors { get { return monitors; } }

        public DisplayCallback()
        {
            monitors = new List<Display>();
        }

        public bool Callback(IntPtr display, IntPtr handle, IntPtr parentDisplay, IntPtr parameters)
        {
            monitors.Add(new Display(display, handle));
            return true;
        }
    }
}